package cn.ibizlab.extensions.ey;

import cn.ibizlab.core.ad.domain.SysPerson;
import cn.ibizlab.core.ad.service.SysPersonService;
import cn.ibizlab.core.authorization.domain.SysRoleMember;
import cn.ibizlab.core.authorization.service.SysRoleMemberService;
import cn.ibizlab.core.system.domain.SysDeploySystem;
import cn.ibizlab.core.system.service.SysDeploySystemService;
import cn.ibizlab.util.security.AuthUser20Impl;
import cn.ibizlab.util.security.AuthenticationUser;
import cn.ibizlab.util.service.AuthenticationUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

@Slf4j
@Component
public class EYTokenFilter extends OncePerRequestFilter {


    @Value("${ibiz.auth.excludesPattern:}")
    private String[] excludesPattens;

    private Set<String> excludesPattern ;
    public Set<String> getExcludesPattern() {
        if(excludesPattern==null) {
            excludesPattern=new HashSet<>(Arrays.asList(excludesPattens));
        }
        return excludesPattern;
    }
    private PathMatcher pathMatcher = new AntPathMatcher();

    @Value("${ibiz.admin.orgid:000000}")
    private String adminorgid;

    @Value("${ibiz.auth.defaultPassword:123456}")
    private String defaultPassword;

    private final AuthenticationUserService userDetailsService;
    private final String tokenHeader;

    public EYTokenFilter(UserDetailsService userDetailsService, @Value("${ibiz.jwt.header:Authorization}") String tokenHeader) {
        this.userDetailsService = (AuthenticationUserService)userDetailsService;
        this.tokenHeader = tokenHeader;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (isExclusion(request.getRequestURI())) {
            chain.doFilter(request, response);
            return;
        }
        if(SecurityContextHolder.getContext().getAuthentication() != null){
            chain.doFilter(request, response);
            return;
        }
        String username = null;
        EYHeader eyHeader = EYHeader.getInstance();
        if(eyHeader!=null) {
            username = eyHeader.getEmail();
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.loadUserByUsername(eyHeader);
                if(userDetailsService instanceof  AuthenticationUserService && userDetails instanceof AuthenticationUser) {
                    ((AuthenticationUser)userDetails).readHeader(false);
                }
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(request, response);
    }

    @Autowired
    SysPersonService sysPersonService;

    @Autowired
    SysDeploySystemService deploySystemService;

    @Autowired
    SysRoleMemberService roleMemberService;


    @Value("${ibiz.auth.username.lowercase:true}")
    private boolean userNameLowerCase;


    public UserDetails loadUserByUsername(EYHeader eyHeader)
    {
        String username = eyHeader.getEmail();
        if(userNameLowerCase)
            username=username.toLowerCase();
        String[] usernameArrays = username.split("[|]");
        String loginname="";
        String domains="";

        LambdaQueryWrapper<SysPerson> conditions = Wrappers.lambdaQuery();
        if(usernameArrays.length>0)
            loginname = usernameArrays[0].trim();
        if(usernameArrays.length>1)
            domains = usernameArrays[1].trim();

        if(!ObjectUtils.isEmpty(loginname))
            conditions.eq(SysPerson::getUid,loginname);
        if(!ObjectUtils.isEmpty(domains))
            conditions.eq(SysPerson::getDc,domains);

        long count = sysPersonService.count(conditions);
        if(count==0) {
            SysPerson authuserdetail = new SysPerson();

            authuserdetail.setEmployeeNumber(IdWorker.getIdStr());
            authuserdetail.setUid(username);
            authuserdetail.setMail(username);
            authuserdetail.setDisplayName(username);
            authuserdetail.setEmployeeType("AAD");
            authuserdetail.setUserPassword(defaultPassword);
            authuserdetail.setOrganizationId(adminorgid);
            authuserdetail.setPostalAddress(eyHeader.getSystemId());
            authuserdetail.setDc(domains);
            authuserdetail.setId(username);
            sysPersonService.create(authuserdetail);
            initBaseRole(getSystemByBindingId(eyHeader.getSystemId()),eyHeader);
            return userDetailsService.loadUserByUsername(username);

        }

        AuthUser20Impl authenticationUser = (AuthUser20Impl)this.userDetailsService.loadUserByUsername(username);
        if(!ObjectUtils.isEmpty(eyHeader.getSystemId())) {
            SysDeploySystem bindingSystem = getSystemByBindingId(eyHeader.getSystemId());
            if(bindingSystem != null && (authenticationUser.getRoles() == null || !authenticationUser.getRoles().stream().filter(e->bindingSystem.getId().equalsIgnoreCase(e.getDeploySystemId())).findAny().isPresent())) {
                initBaseRole(bindingSystem,eyHeader);
                userDetailsService.resetByUsername(username);
                return userDetailsService.loadUserByUsername(username);
            }
        }


        return authenticationUser;
    }

    private SysDeploySystem emptySystem = new SysDeploySystem();

    private SysDeploySystem getSystemByBindingId(String bindingId) {
        if(ObjectUtils.isEmpty(bindingId))
            return null;
        Optional<SysDeploySystem> optional = deploySystemService.all(emptySystem).stream().filter(e->bindingId.equalsIgnoreCase(e.getBindingId())).findAny();
        if(optional.isPresent())
            return optional.get();
        else
            return null;
    }

    private void initBaseRole(SysDeploySystem system,EYHeader eyHeader) {
        if(system != null) {
            String username = eyHeader.getEmail();
            if(userNameLowerCase)
                username=username.toLowerCase();
            String defaultRoleId = String.format("%s||%s"
                    ,String.format("ROLE_%1$s_USERS", system.getSystemId().toUpperCase()),system.getId());
            defaultRoleId = DigestUtils.md5DigestAsHex(defaultRoleId.getBytes());
            if(roleMemberService.count(Wrappers.<SysRoleMember>lambdaQuery().eq(SysRoleMember::getRoleId,defaultRoleId).eq(SysRoleMember::getMemberType,"inetOrgPerson").eq(SysRoleMember::getMemberUid,username))==0) {
                roleMemberService.create(new SysRoleMember().setRoleId(defaultRoleId).setMemberType("inetOrgPerson")
                        .setMemberUid(username)
                        .setMemberName(username));
            }
        }
    }


    public boolean isExclusion(String requestURI) {
        if (this.getExcludesPattern() == null) {
            return false;
        } else {
            for(String pattern:this.getExcludesPattern()) {
                if(pathMatcher.match(pattern, requestURI))
                    return true;
            }
            return false;
        }
    }
}
